{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "custom_callback",
      "provenance": [],
      "private_outputs": true,
      "collapsed_sections": [
        "9xMkm699JzK8"
      ],
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "597OjogAI3fy"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "5bSCD8SyJC2g",
        "colab": {},
        "cellView": "form"
      },
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Rd7BF9BMTJ9l",
        "colab_type": "text"
      },
      "source": [
        "# Escribir callbacks de Keras personalizados"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "E_ceEiH7g0MY"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://www.tensorflow.org/guide/keras/custom_callback\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />Ver en TensorFlow.org</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs/blob/master/site/es/guide/keras/custom_callback.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Ejecutar en Google Colab</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs/blob/master/site/es/guide/keras/custom_callback.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />Ver fuente en GitHub</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a href=\"https://storage.googleapis.com/tensorflow_docs/docs/site/es/guide/keras/custom_callback.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\" />Descargar notebook</a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YLYPmTSZTM5V",
        "colab_type": "text"
      },
      "source": [
        "Note: Nuestra comunidad de Tensorflow ha traducido estos documentos. Como las traducciones de la comunidad\n",
        "son basados en el \"mejor esfuerzo\", no hay ninguna garantia que esta sea un reflejo preciso y actual \n",
        "de la [Documentacion Oficial en Ingles](https://www.tensorflow.org/?hl=en).\n",
        "Si tienen sugerencias sobre como mejorar esta traduccion, por favor envian un \"Pull request\"\n",
        "al siguiente repositorio [tensorflow/docs](https://github.com/tensorflow/docs).\n",
        "Para ofrecerse como voluntario o hacer revision de las traducciones de la Comunidad\n",
        "por favor contacten al siguiente grupo [docs@tensorflow.org list](https://groups.google.com/a/tensorflow.org/forum/#!forum/docs)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "1OykC-6lI4gv"
      },
      "source": [
        "Un callback personalizado es una herramienta poderosa para personalizar el comportamiento de un modelo de Keras durante el entrenamiento, evaluacion o inferencia, incluyendo la lectura/cambio del modelo de Keras. Ejemplos incluyen `tf.keras.callbacks.TensorBoard`, donde se pueden exportar y visualizar el progreso del entrenamiento y los resultados con TensorBoard, o `tf.keras.callbacks.ModelCheckpoint` donde el modelo es automaticamente guardado durante el entrenamiento, entre otros. En esta guia aprenderas que es un callback de Keras, cuando se llama, que puede hacer y como puedes construir una propia. Al final de la guia habra demos para la creacion de aplicaciones simples de callback para ayudarte a empezar tu propio callback personalizados."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "d5zZ8rZD69VW"
      },
      "source": [
        "## Setup"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "7BazS4qD6-2n",
        "colab": {}
      },
      "source": [
        "from __future__ import absolute_import, division, print_function, unicode_literals\n",
        "\n",
        "try:\n",
        "  # %tensorflow_version only exists in Colab.\n",
        "  %tensorflow_version 2.x\n",
        "except Exception:\n",
        "  pass\n",
        "import tensorflow as tf"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "0c_TYhQOUe1j"
      },
      "source": [
        "## Introduccion a los callbacks de Keras\n",
        "En Keras 'Callback' es una clase de python destinada a ser subclase para proporcionar una funcionalidad específica, con un conjunto de métodos llamados en varias etapas de entrenamiento (incluyendo el inicio y fin de los batch/epoch), pruebas y predicciones. Los Callbacks son útiles para tener visibilidad de los estados internos y las estadísticas del modelo durante el entrenamiento. Puedes pasar una lista de callbacks (como argumento de palabra clave `callbacks`) a cualquiera de los siguientes metodos ` tf.keras.Model.fit () `,` tf.keras.Model.evaluate () `y` tf.keras.Model .predict () `. Los metodos de los callbacks se llamaran en diferentes etapas del entrenamiento/evaluación/inferencia.\n",
        "\n",
        "Para comenzar, importemos TensorDlow y definamos un modelo secuencial sencillo en Keras:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "Ct0VCSI2dt3a",
        "colab": {}
      },
      "source": [
        "# Definir el modelo de Keras model al que se le agregaran los callbacks\n",
        "def get_model():\n",
        "  model = tf.keras.Sequential()\n",
        "  model.add(tf.keras.layers.Dense(1, activation = 'linear', input_dim = 784))\n",
        "  model.compile(optimizer=tf.keras.optimizers.RMSprop(lr=0.1), loss='mean_squared_error', metrics=['mae'])\n",
        "  return model"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "ySzdG1IqNgah"
      },
      "source": [
        "Luego, cara el dataset de  MNIST para entrenamiento y pruebas de la APLI de datasetws de Keras:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "fwo9LMKGNPWr",
        "colab": {}
      },
      "source": [
        "# Cargar los datos de ejemplo de MNIST data y preprocesarlos\n",
        "(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()\n",
        "x_train = x_train.reshape(60000, 784).astype('float32') / 255\n",
        "x_test = x_test.reshape(10000, 784).astype('float32') / 255"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "kHVK7kceNqH2"
      },
      "source": [
        "Ahora, define un callback simple y personalizado para rastrear el inicio y fin de cada batch de datos. Durante esas llamadas, imprime el indice del batch actual."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "-dfuGTMINKRR",
        "colab": {}
      },
      "source": [
        "import datetime\n",
        "\n",
        "class MyCustomCallback(tf.keras.callbacks.Callback):\n",
        "\n",
        "  def on_train_batch_begin(self, batch, logs=None):\n",
        "    print('Entrenamiento: batch {} comienza en {}'.format(batch, datetime.datetime.now().time()))\n",
        "\n",
        "  def on_train_batch_end(self, batch, logs=None):\n",
        "    print('Entrenamiento: batch {} termina en {}'.format(batch, datetime.datetime.now().time()))\n",
        "\n",
        "  def on_test_batch_begin(self, batch, logs=None):\n",
        "    print('Evaluacion: batch {} comienza en {}'.format(batch, datetime.datetime.now().time()))\n",
        "\n",
        "  def on_test_batch_end(self, batch, logs=None):\n",
        "    print('Evaluacion: batch {} termina en {}'.format(batch, datetime.datetime.now().time()))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Z4FTUUIBN3WG"
      },
      "source": [
        "Dar un callback mara los metodos del modelo tales como `tf.keras.Model.fit()` aseguran que los metodos son llamados en dichas etapas:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "NJV6Tj3sNGzg",
        "colab": {}
      },
      "source": [
        "model = get_model()\n",
        "_ = model.fit(x_train, y_train,\n",
        "          batch_size=64,\n",
        "          epochs=1,\n",
        "          steps_per_epoch=5,\n",
        "          verbose=0,\n",
        "          callbacks=[MyCustomCallback()])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "fIy5JKMlZNmh"
      },
      "source": [
        "## Metodos del Modelo que aceptan callbacks\n",
        "Los usuarios pueden dar una lista de callbacks para los siguientes metodos de `tf.keras.Model`:\n",
        "#### [`fit()`](https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/keras/Model#fit), [`fit_generator()`](https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/keras/Model#fit_generator)\n",
        "Entrena el modelo por una cantidad determinada de epochs  (iteraciones en un dataset, o para los datos determinados por un generador de Python que va batch-por-batch).\n",
        "#### [`evaluate()`](https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/keras/Model#evaluate), [`evaluate_generator()`](https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/keras/Model#evaluate_generator)\n",
        "Evalua el modelo para determinados datos o generador de datos. Regresa la perdida (loss) y valores metricos para la evaluacion.\n",
        "#### [`predict()`](https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/keras/Model#predict), [`predict_generator()`](https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/keras/Model#predict_generator)\n",
        "Genera las predicciones a regresar para los datos ingresados o el generador de datos.\n",
        "NOTA: Toda la documentacion esta en ingles.\n"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "J00bXBbqdnJe",
        "colab": {}
      },
      "source": [
        "_ = model.evaluate(x_test, y_test, batch_size=128, verbose=0, steps=5,\n",
        "          callbacks=[MyCustomCallback()])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "13n44LVkYQsV"
      },
      "source": [
        "## Una revision de los metodos de callback\n",
        "\n",
        "\n",
        "### Metodos comunes para entrenamiento/pruebas/prediccion\n",
        "Para entrenamiento, pruebas y prediccion, los siguientes metodos se han previsto para ser sobreescritos.\n",
        "#### `on_(train|test|predict)_begin(self, logs=None)`\n",
        "Llamado al inicio de `fit`/`evaluate`/`predict`.\n",
        "#### `on_(train|test|predict)_end(self, logs=None)`\n",
        "Llamado al fin de `fit`/`evaluate`/`predict`.\n",
        "#### `on_(train|test|predict)_batch_begin(self, batch, logs=None)`\n",
        "Llamado justo antes de procesar un batch durante entrenamiento/pruebas/prediccion. Dentro de este metodo, `logs` es un diccionario con las llaves `batch` y `size` disponibles, representando el numero de batch actual y las dimensiones del mismo.\n",
        "#### `on_(train|test|predict)_batch_end(self, batch, logs=None)`\n",
        "Llamado al final del entrenamiento/pruebas/prediccion de un batch. dentro de este metodo, `logs` es un diccionario que contiene resultados metricos con estado.\n",
        "\n",
        "### Entrenamiento de metodos especificos\n",
        "Adicionalmente, para el entrenamiento, los siguientes metodos son provistos.\n",
        "#### on_epoch_begin(self, epoch, logs=None)\n",
        "Llamado al inicio de una epoch durante el entrenamiento.\n",
        "#### on_epoch_end(self, epoch, logs=None)\n",
        "Llamado al final de una epoch durante el entrenamiento.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "SWf3mXYoceCz"
      },
      "source": [
        "### Uso del diccionario `logs`\n",
        "El diccionario `logs` contiene el valor de perdida (loss), y todas las metricas pertinentes al final de un batch o epoch. El ejemplo a continuacion incluye la perdidad (loss) y el MAE (Mean Absolute Error)."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "u4wIdcF9BjJH",
        "colab": {}
      },
      "source": [
        "class LossAndErrorPrintingCallback(tf.keras.callbacks.Callback):\n",
        "\n",
        "  def on_train_batch_end(self, batch, logs=None):\n",
        "    print('Para el batch {}, la perdida (loss) es {:7.2f}.'.format(batch, logs['loss']))\n",
        "\n",
        "  def on_test_batch_end(self, batch, logs=None):\n",
        "    print('Para el  batch {}, la perdida (loss) es {:7.2f}.'.format(batch, logs['loss']))\n",
        "\n",
        "  def on_epoch_end(self, epoch, logs=None):\n",
        "    print('La perdida promedio para la epoch {} es {:7.2f} y el MAE es {:7.2f}.'.format(epoch, logs['loss'], logs['mae']))\n",
        "\n",
        "model = get_model()\n",
        "_ = model.fit(x_train, y_train,\n",
        "          batch_size=64,\n",
        "          steps_per_epoch=5,\n",
        "          epochs=3,\n",
        "          verbose=0,\n",
        "          callbacks=[LossAndErrorPrintingCallback()])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "LbXqvC8FHqeu"
      },
      "source": [
        "De manera similar, uno puede proveer callbacks en las llamadas a `evaluate()`."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "jzTKYPQHwcxF",
        "colab": {}
      },
      "source": [
        "_ = model.evaluate(x_test, y_test, batch_size=128, verbose=0, steps=20,\n",
        "          callbacks=[LossAndErrorPrintingCallback()])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "HnSljqtsXKfb"
      },
      "source": [
        "## Ejemplos de aplicaciones de callbacks de Keras\n",
        "La siguiente seccion te guiara en la creacion de una aplicacion de callback simple."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "kptNF0--Lznv"
      },
      "source": [
        "### Detencion anticipada con perdida minima.\n",
        "El primer ejemplo muestra la creacion de un `Callback` que detiene el entrenamiento de Keras cuando se alcanza el minimo de perdida mutando el atributo` model.stop_training` (boolean). Opcionalmente, el usuario puede proporcionar el argumento `patience` para especificar cuantas epochs debe esperar el entrenamiento antes de detenerse.\n",
        "\n",
        "`tf.keras.callbacks.EarlyStopping` proporciona una implementación mas completa y general."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "BM31gfAV4mks",
        "colab": {}
      },
      "source": [
        "import numpy as np\n",
        "\n",
        "class EarlyStoppingAtMinLoss(tf.keras.callbacks.Callback):\n",
        "  \"\"\"Detener el entrenamiento cuando la perdida (loss) esta en su minimo, i.e. la perdida (loss) deja de disminuir.\n",
        "\n",
        "  Arguments:\n",
        "      patience: Numero de epochs a esperar despues de que el min ha sido alcanzaado. Despues de este numero\n",
        "      de no mejoras, el entrenamiento para.\n",
        "  \"\"\"\n",
        "\n",
        "  def __init__(self, patience=0):\n",
        "    super(EarlyStoppingAtMinLoss, self).__init__()\n",
        "\n",
        "    self.patience = patience\n",
        "\n",
        "    # best_weights para almacenar los pesos en los cuales ocurre la perdida minima.\n",
        "    self.best_weights = None\n",
        "\n",
        "  def on_train_begin(self, logs=None):\n",
        "    # El numero de epoch que ha esperado cuando la perdida ya no es minima.\n",
        "    self.wait = 0\n",
        "    # El epoch en el que en entrenamiento se detiene.\n",
        "    self.stopped_epoch = 0\n",
        "    # Initialize el best como infinito.\n",
        "    self.best = np.Inf\n",
        "\n",
        "  def on_epoch_end(self, epoch, logs=None):\n",
        "    current = logs.get('loss')\n",
        "    if np.less(current, self.best):\n",
        "      self.best = current\n",
        "      self.wait = 0\n",
        "      # Guardar los mejores pesos si el resultado actual es mejor (menos).\n",
        "      self.best_weights = self.model.get_weights()\n",
        "    else:\n",
        "      self.wait += 1\n",
        "      if self.wait >= self.patience:\n",
        "        self.stopped_epoch = epoch\n",
        "        self.model.stop_training = True\n",
        "        print('Restaurando los pesos del modelo del final de la mejor epoch.')\n",
        "        self.model.set_weights(self.best_weights)\n",
        "\n",
        "  def on_train_end(self, logs=None):\n",
        "    if self.stopped_epoch > 0:\n",
        "      print('Epoch %05d: Detencion anticipada' % (self.stopped_epoch + 1))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "xS4fa-7PFzzc",
        "colab": {}
      },
      "source": [
        "model = get_model()\n",
        "_ = model.fit(x_train, y_train,\n",
        "          batch_size=64,\n",
        "          steps_per_epoch=5,\n",
        "          epochs=30,\n",
        "          verbose=0,\n",
        "          callbacks=[LossAndErrorPrintingCallback(), EarlyStoppingAtMinLoss()])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "SpVDjs_Dkkdh"
      },
      "source": [
        "### Programacion del Learning Rate\n",
        "\n",
        "Algo que es hecho comunmente en el entrenamiento de un modelo es cambiar el learning rate conforme pasan mas epochs. El backend de Keras expone la API `get_value` la cual puede ser usada para definir las variables. En este ejemplo estamos mostrando como un Callback personalizado puede ser usado para cambiar dinamicamente el learning rate.\n",
        "\n",
        "Nota: este es solo una implementacion de ejemplo, `callbacks.LearningRateScheduler` y `keras.optimizers.schedules` contienen implementaciones mas generales."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "PGowEUC8klSz",
        "colab": {}
      },
      "source": [
        "class LearningRateScheduler(tf.keras.callbacks.Callback):\n",
        "  \"\"\"Planificador de Learning rate que define el learning rate deacuerdo a lo programado.\n",
        "\n",
        "  Arguments:\n",
        "      schedule: una funcion que toma el indice del epoch\n",
        "          (entero, indexado desde 0) y el learning rate actual\n",
        "          como entradas y regresa un nuevo learning rate como salida (float).\n",
        "  \"\"\"\n",
        "\n",
        "  def __init__(self, schedule):\n",
        "    super(LearningRateScheduler, self).__init__()\n",
        "    self.schedule = schedule\n",
        "\n",
        "  def on_epoch_begin(self, epoch, logs=None):\n",
        "    if not hasattr(self.model.optimizer, 'lr'):\n",
        "      raise ValueError('Optimizer must have a \"lr\" attribute.')\n",
        "    # Obtener el learning rate actua del optimizer del modelo.\n",
        "    lr = float(tf.keras.backend.get_value(self.model.optimizer.lr))\n",
        "    # Llamar la funcion schedule para obtener el learning rate programado.\n",
        "    scheduled_lr = self.schedule(epoch, lr)\n",
        "    # Definir el valor en el optimized antes de que la epoch comience\n",
        "    tf.keras.backend.set_value(self.model.optimizer.lr, scheduled_lr)\n",
        "    print('\\nEpoch %05d: Learning rate is %6.4f.' % (epoch, scheduled_lr))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "1jL3pI5Ep5F8",
        "colab": {}
      },
      "source": [
        "LR_SCHEDULE = [\n",
        "    # (epoch a comenzar, learning rate) tupla\n",
        "    (3, 0.05), (6, 0.01), (9, 0.005), (12, 0.001)\n",
        "]\n",
        "\n",
        "def lr_schedule(epoch, lr):\n",
        "  \"\"\"Funcion de ayuda para recuperar el learning rate programado basado en la epoch.\"\"\"\n",
        "  if epoch < LR_SCHEDULE[0][0] or epoch > LR_SCHEDULE[-1][0]:\n",
        "    return lr\n",
        "  for i in range(len(LR_SCHEDULE)):\n",
        "    if epoch == LR_SCHEDULE[i][0]:\n",
        "      return LR_SCHEDULE[i][1]\n",
        "  return lr\n",
        "\n",
        "model = get_model()\n",
        "_ = model.fit(x_train, y_train,\n",
        "          batch_size=64,\n",
        "          steps_per_epoch=5,\n",
        "          epochs=15,\n",
        "          verbose=0,\n",
        "          callbacks=[LossAndErrorPrintingCallback(), LearningRateScheduler(lr_schedule)])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "9xMkm699JzK8"
      },
      "source": [
        "### Callbacks de Keras estandar\n",
        "Asegurate de revisar los callbacks de Keras preexistentes [visitando la documentacion de la api](https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/keras/callbacks). Las aplicaciones incluyen el registro a CSV, guardar el modelo, visualizar en TensorBoard y mucho mas.\n",
        "\n",
        "NOTA: La documentacion aun esta en ingles"
      ]
    }
  ]
}